package Flap::Schema;
use Flap qw/-m -s -w/;
use constant DEFAULT_OPTIONS_ON_DELETE => {};
use constant DEFAULT_OPTIONS_ON_INSERT => {};
use constant DEFAULT_OPTIONS_ON_UPDATE => {
    -f => 0, #force
    -l => 1, #lock
};

sub delete {
    die $_[0]->_dmsg_(2) if !ref $_[0];
    my $row = shift;
    my $opt = {%{$row->DEFAULT_OPTIONS_ON_DELETE}, on => 'delete'};
    $row->_merge_options_($opt, shift) if @_;
    
    return !1 if $opt->{-v} && !$row->is_valid($opt);
    $row->_delete_($opt);
}

sub _delete_ { $_[0]->storage->delete(@_) }

sub get_column {
    die $_[0]->_dmsg_(2) if !ref $_[0];
    my($row, $col) = @_;
    my $h = $row->{-inf};
    return $h->{$col} if $h && exists $h->{$col};
    my $ci = $row->_global_->{column_info}{$col} || die $row->_dmsg_(5, $col);
    unless(($h = $row->{-txn}) && exists $h->{$col}){
        die $row->_dmsg_(6, $col) if ($h = $row->{-org}) && !exists $h->{$col};
    }
    return $h->{$col} if !$ci->{-i};
    $row->{-inf}{$col} = $row->storage->${$ci->{-i}[0]}($h->{$col}, $row, $col); #inflate
}

sub has_column_loaded {
    die $_[0]->_dmsg_(2) if !ref $_[0];
    my $row = shift;
    my $h;
    ($h = $row->{-txn}) && exists $h->{$_[0]} || ($h = $row->{-org}) && exists $h->{$_[0]};
}

sub insert {
    die $_[0]->_dmsg_(2) if !ref $_[0];
    my $row = shift;
    my $opt = {%{$row->DEFAULT_OPTIONS_ON_INSERT}, on => 'insert'};
    $row->_merge_options_($opt, shift) if @_;
    
    $row->_insert_($opt);
}

sub _insert_ { $_[0]->storage->insert(@_) }

sub in_storage { ref $_[0] ? $_[0]->{-org} && 1 : die $_[0]->_dmsg_(2) }

sub is_changed {
    die $_[0]->_dmsg_(2) if !ref $_[0];
    my $h = shift->{-txn};
    $h && %$h && 1;
}

sub is_column_changed {
    die $_[0]->_dmsg_(2) if !ref $_[0];
    my $h = shift->{-txn};
    $h && exists $h->{$_[0]};
}

sub is_valid {
    die $_[0]->_dmsg_(2) if !ref $_[0];
    1;
}

sub save {
    my $row = ref $_[0] ? shift : die $_[0]->_dmsg_(2);
    $row->{-org} ? $row->update(@_) : $row->insert(@_);
}

sub set_column {
    die $_[0]->_dmsg_(2) if !ref $_[0];
    my($row, $col, $v) = @_;
    my $ci = $row->_global_->{column_info}{$col} || die $row->_dmsg_(5, $col);
    my $iv;
    if(ref $v){
        my $i = $ci->{-i} || die qq{Can't set $v to non-inflate column "$col"};
        $iv = $row->{-inf}{$col} = $v;
        $v = $row->storage->${$i->[1]}($iv, $row, $col); #deflate
    }
    
    my $dv = defined $v;
    my $h = $row->{-org};
    if($h && exists $h->{$col} && !($dv xor defined $h->{$col}) && (!$dv || $v eq $h->{$col})){
        delete $h->{$col} if ($h = $row->{-txn});
        return $row;
    }
    delete $h->{$col} if $ci->{-i} && !$iv && ($h = $row->{-inf});
    $row->store_column($col, $v);
    $row;
}

sub _set_current_time_ {
    my($row, $cols, $txn) = @_;
    my $cih = $row->_global_->{column_info};
    my $now;
    foreach(@$cols){
        next if !$cih->{$_} || $txn && exists $txn->{$_};
        $row->set_column($_, $now ? $now->clone : $now = $row->project->now);
    }
}

sub store_column { $_[0]->{-txn}{$_[1]} = $_[2] }

sub update {
    die $_[0]->_dmsg_(2) if !ref $_[0];
    my $row = shift;
    my $opt = {%{$row->DEFAULT_OPTIONS_ON_UPDATE}, on => 'update'};
    $row->_merge_options_($opt, shift) if @_;
    
    return 1 if !$row->is_changed && !$opt->{-f};
    $row->_update_($opt);
}

sub _update_ {
    my($row, $opt) = @_;
    
    $row->_set_current_time_($row->SET_CURRENT_TIME_ON_UPDATE);
    $row->storage->update(@_);
}

1;
